using System;
using System.Net;
using System.Net.Sockets;
using SmartSite.Network.Properties;

namespace SmartSite.Network
{
    public class AsyncSocket : IDisposable, IAsyncSocket
    {
        public int Port { get; }
        public int BufferSize { get; }
        readonly Socket Socket;
        bool isClosed = true;
        bool disposed = false;

        /// <summary>
        /// 接收数据完成.
        /// </summary>
        public event EventHandler<SocketStateEventArgs> OnReceived;

        /// <summary>
        /// 错误事件
        /// </summary>
        public event EventHandler<SocketErrorEventArgs> OnError;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="port">接收和发送端口</param>
        /// <param name="bufferSize">缓冲区最小大小</param>
        public AsyncSocket(int port, int bufferSize)
        {
            Port = port;
            BufferSize = bufferSize;

            Socket = new Socket(
                AddressFamily.InterNetwork,
                SocketType.Dgram,
                ProtocolType.Udp
            );

            IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, Port);
            Socket.Bind(localEndPoint);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposed) return;

            disposed = true;

            if (disposing)
            {
                Socket.Dispose();
            }

            StopReceiving();
        }

        public void StartReceiving()
        {
            isClosed = false;
            RepeatedReceiving();
        }

        void RepeatedReceiving()
        {
            EndPoint endPoint = new IPEndPoint(IPAddress.Any, 0);
            byte[] buffer = new byte[BufferSize];
            SocketStateEventArgs state = new AsyncSocketStateEventArgs(this, endPoint, buffer);

            try
            {
                if (!isClosed)
                {
                    Socket.BeginReceiveFrom(
                        buffer, 0, buffer.Length,
                        SocketFlags.None,
                        ref endPoint,
                        new AsyncCallback(AsyncCallback_ReceiveFrom),
                        state
                    );
                }
            }
            catch (SocketException e)
            {
                RaiseExcption(state, e);
            }
        }

        public void StopReceiving()
        {
            isClosed = true;
        }

        void AsyncCallback_ReceiveFrom(IAsyncResult asyncResult)
        {
            SocketStateEventArgs state = (SocketStateEventArgs)asyncResult.AsyncState;
            EndPoint remoteEndPoint = state.RemoteEndPoint;
            int count = 0;
            try
            {
                count = Socket.EndReceiveFrom(
                    asyncResult, ref remoteEndPoint);
            }
            catch (SocketException e)
            {
                RaiseExcption(state, e);
            }

            state = new AsyncSocketStateEventArgs(state.Socket, remoteEndPoint, state.GetBuffer(count));
            if (!isClosed)
            {
                RepeatedReceiving();

                if (count > 0)
                    OnReceived?.Invoke(this, state);
                else
                    RaiseError(state, Resources.String1);
            }
        }

        public void SendTo(IPAddress remoteIP, byte[] buffer)
            => SendTo(remoteIP, Port, buffer);

        public void SendTo(IPAddress remoteIP, int port, byte[] buffer)
            => SendTo(new IPEndPoint(remoteIP, port), buffer);

        public void SendTo(EndPoint remoteEndPoint, byte[] buffer)
        {
            SocketStateEventArgs state = new AsyncSocketStateEventArgs(this, remoteEndPoint, buffer);
            try
            {
                Socket.BeginSendTo(
                    buffer, 0, buffer.Length,
                    SocketFlags.None,
                    remoteEndPoint,
                    new AsyncCallback(AsyncCallBack_SendTo),
                    state
                );
            }
            catch (SocketException e)
            {
                RaiseError(state, e.Message);
            }
        }

        void AsyncCallBack_SendTo(IAsyncResult asyncResult)
        {
            int sendLength = Socket.EndSendTo(asyncResult);
            SocketStateEventArgs state = (SocketStateEventArgs)asyncResult.AsyncState;
            if (state.BufferLength != sendLength) RaiseError(state, Resources.String2);
            else if (!asyncResult.IsCompleted) RaiseError(state, Resources.String3);
        }

        void RaiseError(SocketStateEventArgs state, string message)
        {
            OnError?.Invoke(this, new SocketErrorEventArgs(state, message));
        }

        void RaiseExcption(SocketStateEventArgs state, Exception e)
        {
            RaiseError(state, "接收数据发生异常" + e.Message);
        }
    }
}
